(function (globalThis) {

  /** 鼠标悬浮内容 */
  const HoverType = {
    /** 无内容 */
    None: 0,
    /** 节点 */
    Node: 1,
    /** 节点缩放控制点 */
    Resize: 2,
    /** 节点缩放控制点(保持横纵比) */
    ResizeByRatio: 3,
    /** 节点旋转控制点 */
    Rotate: 4,
    /** 节点删除控制点 */
    Remove: 5,
    /** 节点复制制点 */
    Copy: 6,
  }

  /** 鼠标右键操作类型 */
  const MouseRight = {
    /** 没有点击 */
    None: 0,
    /** 普通点击 */
    Down: 1,
    /** 平移 */
    Translate: 2,
  }

  const EventType = {
    /** 打开一张图 */
    OPEN: "open",
    /** 界面大小变化 */
    RESIZE: "resize",
    /** 鼠标移入 */
    ENTER: "enter",
    /** 鼠标移出 */
    LEAVE: "leave",
    /** 选中节点 */
    ACTIVE: "active",
    /** 取消选中节点 */
    INACTIVE: "inactive",
    /** 旋转节点 */
    ROTATE_NODES: "rotateNodes"
  }

  /** 撤销重做类型 */
  const EditType = {
    /** 新增 */
    Add: 0,
    /** 修改 */
    Update: 1,
    /** 删除 */
    Delete: 2,
  }

  const inheritanceProps = [
    'color',
    'fontFamily',
    'fontSize',
    'fontStyle',
    'fontWeight',
    'lineHeight',
  ]

  function ImageDesigner(el, options) {

    /** 初始化 */
    this.init = function (el, options) {
      options = Object.assign({
        width: 1920,
        height: 1080
      }, options || {})
      this.el = typeof el == 'string' ? document.getElementById(el) : el
      this.store = useStore(s8())
      this.canvas = new ImageDesigner.Canvas(this.el, this.store)
      this.canvas.resize(options.width, options.height)
    }

    /** 打开一个图纸 */
    this.open = function (data) {
      this.clear()
      if (data) {
        this.setBackgroundImage(data.backgroundImage)
        Object.assign(this.store.data, data)
        this.store.data.pens = []
        for (const pen of data.pens) {
          this.canvas.makePen(pen)
        }
        this.canvas.resize()
      }
      this.canvas.render()
    }

    /** 设置背景图片 */
    this.setBackgroundImage = function (url) {
      this.store.data.backgroundImage = url
      this.store.backgroundImg = null
      if (url) {
        loadImage(url, img => {
          this.store.backgroundImg = img
          this.canvas.render()
        })
      }
    }

    /** 修改节点属性值 */
    this.setValue = function (data, render = true, doEvent = true, history) {
      let pens = []
      if (data.id) {
        const pen = this.store.pens[data.id]
        pen && (pens = [pen])
      } else {
        return
      }

      let initPens
      if (history) {
        initPens = deepClone(pens)
      }
      pens.forEach((pen) => {
        this.canvas.updateValue(pen, data)
      })

      if (this.store.active.length && !this.canvas.movingPens) {
        // 移动过程中，不重算 activeRect
        this.canvas.calcActiveRect()
      }

      if (history) {
        this.canvas.pushHistory({
          type: EditType.Update,
          initPens,
          pens
        })
      }
      if (doEvent) {
        pens.forEach((pen) => {
          this.store.emitter.emit('valueUpdate', pen)
        })
      }
      if (render) {
        this.render()
      }
    }

    /** 清空内容 */
    this.clear = function () {
      clearStore(this.store)
      this.canvas.clear()
    }

    /** 复制节点不传则为当前选中 */
    this.copy = function (pens) {
      this.canvas.copy(pens);
    }

    /** 剪切节点不传则为当前选中 */
    this.cut = function (pens) {
      this.canvas.cut(pens)
    }

    /** 粘贴节点 */
    this.paste = function () {
      this.canvas.paste()
    }

    /** 撤回上一步 */
    this.undo = function () {
      this.canvas.undo();
    }

    /** 重做下一步 */
    this.redo = function () {
      this.canvas.redo();
    }

    /** 选中节点 */
    this.active = function (pens, emit = true) {
      this.canvas.active(pens, emit);
    }

    /** 取消选中 */
    this.inactive = function () {
      this.canvas.inactive();
    }

    /** 全选 */
    this.activeAll = function () {
      this.canvas.active(this.store.data.pens);
      this.render();
    }

    /**
     * 锁定
     *
     * @param pens? 需要锁定的节点 默认取选中的
     * @author zhengqiang
     */
    this.lock = function (pens) {
      if (!pens) pens = this.store.active
      if (!Array.isArray(pens)) pens = [pens]
      const allPens = this.store.data.pens;
      for (const pen of pens) {
        const index = allPens.findIndex(p => p.id === pen.id)
        if (index > -1) {
          allPens[index].locked = true
        }
      }
      this.render()
    }

    /**
     * 解锁
     *
     * @param pens? 需要解锁的节点 默认取选中的
     * @author zhengqiang
     */
    this.unLock = function (pens) {
      if (!pens) pens = this.store.active
      if (!Array.isArray(pens)) pens = [pens]
      const allPens = this.store.data.pens;
      for (const pen of pens) {
        const index = allPens.findIndex(p => p.id === pen.id)
        if (index > -1) {
          allPens[index].locked = false
        }
      }
      this.render()
    }

    /** 开启多选 */
    this.startMultiSelect = function () {
      this.canvas.multiSelect = true
    }

    /** 结束多选 */
    this.stopMultiSelect = function () {
      this.canvas.multiSelect = false
    }

    /** 设置添加文本，下次屏幕点击区域会添加文本 */
    this.addText = function(text, fontSize, color, fontFamily) {
      this.canvas.addCaches = [
        {
          type: 'text',
          text: text || '这是一个文本',
          fontSize: fontSize || 14,
          color: color || 'black',
          fontFamily: fontFamily || 'Arial',
        }
      ]
    }

    /**
     * 添加节点
     *
     * @param pens 节点内容
     * @param history 是否记录历史
     * @author zhengqiang
     */
    this.addPens = function(pens, history) {
      this.canvas.addPens(pens, history)
    }

    /**
     * 删除节点
     *
     * @param pens? 需要删除的节点 默认取选中的
     * @param canDelLocked? 是否删除锁定的节点，默认false
     * @param history? 是否记录history 默认true
     * @author zhengqiang
     */
    this.delete = function (pens, canDelLocked, history) {
      this.canvas.delete(pens, canDelLocked, history);
    }

    /**
     * 置顶
     *
     * @param pens? 需要置顶的节点 默认取选中的
     * @author zhengqiang
     */
    this.top = function (pens) {
      if (!pens) pens = this.store.active
      if (!Array.isArray(pens)) pens = [pens]
      const allPens = this.store.data.pens;
      for (const pen of pens) {
        const index = allPens.findIndex(p => p.id === pen.id)
        if (index > -1) {
          allPens.push(allPens[index]);
          allPens.splice(index, 1);
        }
      }
      this.render()
    }

    /**
     * 置底
     *
     * @param pens? 需要置底的节点 默认取选中的
     * @author zhengqiang
     */
    this.bottom = function (pens) {
      if (!pens) pens = this.store.active
      if (!Array.isArray(pens)) pens = [pens]
      const allPens = this.store.data.pens;
      for (const pen of pens) {
        const index = allPens.findIndex(p => p.id === pen.id)
        if (index > -1) {
          let temp = allPens[index]
          allPens.splice(index, 1);
          allPens.unshift(temp)
        }
      }
      this.render()
    }

    /**
     * 上移节点图层
     *
     * @param pens? 需要置底的节点 默认取选中的
     * @author zhengqiang
     */
    this.up = function (pens) {
      if (!pens) pens = this.store.active
      if (!Array.isArray(pens)) pens = [pens]
      const allPens = this.store.data.pens;
      for (const pen of pens) {
        const index = allPens.findIndex(p => p.id === pen.id)
        if (index > -1) {
          allPens.splice(index + 2, 0, allPens[index]);
          allPens.splice(index, 1)
        }
      }
      this.render()
    }

    /**
     * 下移节点图层
     *
     * @param pens? 需要置底的节点 默认取选中的
     * @author zhengqiang
     */
    this.down = function (pens) {
      if (!pens) pens = this.store.active
      if (!Array.isArray(pens)) pens = [pens]
      const allPens = this.store.data.pens;
      for (const pen of pens) {
        const index = allPens.findIndex(p => p.id === pen.id)
        if (index > 0) {
          allPens.splice(index - 1, 0, allPens[index]);
          allPens.splice(index + 1, 1)
        }
      }
      this.render()
    }

    /**
     * 通过计算重叠区域上移节点图层
     *
     * @param pen 节点
     * @author zhengqiang
     */
    this.upByArea = function (pen) {
      const index = this.store.data.pens.findIndex((p) => p.id === pen.id);
      if (index === -1) {
        return;
      }

      const penRect = pen.calculative.worldRect;
      const nextHitIndex = this.store.data.pens.findIndex((p, i) => {
        if (i <= index) {
          // 不考虑前面的
          return false;
        }
        if (p.id === pen.id) {
          // 不考虑自身
          return false;
        }
        const currentRect = p.calculative.worldRect;
        return rectInRect(penRect, currentRect);
      });

      if (nextHitIndex == -1) {
        this.up(pen);
        return;
      }

      this.store.data.pens.splice(nextHitIndex + 1, 0, pen);
      this.store.data.pens.splice(index, 1);
    }

    /**
     * 缩放画布
     *
     * @param scale 缩放比例
     * @param center? 中心点
     * @author zhengqiang
     */
    this.scale = function (scale, center) {
      this.canvas.scale(scale, center);
    }

    /**
     * 平移画布
     *
     * @param x 横向移动大小
     * @param y 纵向移动大小
     * @author zhengqiang
     */
    this.translate = function (x, y) {
      this.canvas.translate(x, y);
    }

    /**
     * 平移节点
     *
     * @param pens 需要移动的节点
     * @param x 横向移动大小
     * @param y 纵向移动大小
     * @author zhengqiang
     */
    this.translatePens = function (pens, x, y) {
      this.canvas.translatePens(pens, x, y);
    }

    /** 导出数据 */
    this.data = function () {
      return deepClone(this.store.data);
    }

    /** 导出数据 */
    this.toPng = function (callback) {
      return this.canvas.toPng(callback)
    }

    /** 手动触发渲染 */
    this.render = function (patchFlags) {
      this.canvas.render(patchFlags)
    }

    /**
     * 监听事件
     *
     * @param eventName 事件名称 *: 全部事件
     * @param handler 回调函数
     * @author zhengqiang
     */
    this.on = function(eventName, handler) {
      this.store.emitter.on(eventName, handler)
    }

    /**
     * 监听一次事件
     *
     * @param eventName 事件名称 *: 全部事件
     * @param handler 回调函数
     * @author zhengqiang
     */
    this.once = function(eventName, handler) {
      this.store.emitter.once(eventName, handler)
    }

    /**
     * 取消监听事件
     *
     * @param eventName 事件名称
     * @param handler 回调函数
     * @author zhengqiang
     */
    this.off = function(eventName, handler) {
      this.store.emitter.off(eventName, handler)
    }

    this.init(el, options)
  }

  ImageDesigner.Canvas = function (parentElement, store) {

    /** 记录历史 */
    this.pushHistory = function (action) {
      action.scale = this.store.data.scale

      if (action.type !== EditType.Update && action.pens) {
        action.pens.forEach((pen) => {
          if (pen.calculative) {
            pen.calculative.layer = this.store.data.pens.findIndex((p) => p.id === pen.id)
          }
        })
      }

      if (this.store.historyIndex < this.store.histories.length - 1) {
        this.store.histories.splice(this.store.historyIndex + 1, this.store.histories.length - this.store.historyIndex - 1)
      }
      action.pens?.forEach((pen) => {
        let found
        if (action.initPens) {
          for (const p of action.initPens) {
            if (p.id === pen.id) {
              found = p
            }
          }
        }
        if (found) {
          for (const k of Object.keys(pen)) {
            if (found[k] == undefined) {
              found[k] = undefined
            }
          }
        }
      })
      this.store.histories.push(action)
      this.store.historyIndex = this.store.histories.length - 1
      this.store.emitter.emit('update', {
        previous: action.initPens,
        current: action.pens,
      })
    }

    /** 撤销 */
    this.undo = function () {
      if (
        this.store.historyIndex == null ||
        this.store.historyIndex < 0
      ) {
        return
      }

      const action = this.store.histories[this.store.historyIndex--]
      this.doEditAction(action, true)
      let step = action.step
      while (step > 1) {
        const action = this.store.histories[this.store.historyIndex--]
        this.doEditAction(action, true)
        step--
      }
      if (action.type == EditType.Add || action.type == EditType.Delete) {
        this.activeHistory()
      }
    }

    /** 重做 */
    this.redo = function () {
      if (
        this.store.historyIndex == null ||
        this.store.historyIndex > this.store.histories.length - 2
      ) {
        return
      }

      const action = this.store.histories[++this.store.historyIndex]
      this.doEditAction(action, false)
      let step = action.step
      while (step > 1) {
        const action = this.store.histories[++this.store.historyIndex]
        this.doEditAction(action, false)
        step--
      }
      if (action.type == EditType.Add || action.type == EditType.Delete) {
        this.activeHistory()
      }
    }

    /** 选中历史 */
    this.activeHistory = function () {
      let before = this.store.histories[this.store.historyIndex]
      if (before && before.type === EditType.Add) {
        const pens = []
        before.pens.forEach((pen) => {
          pens.push(this.store.pens[pen.id])
        })
        this.active(pens)
      }
    }

    /** 执行撤销/重做 */
    this.doEditAction = function (action, undo) {
      this.inactive()
      this.store.hover = undefined

      switch (action.type) {
        case EditType.Add:
          action.pens.forEach((aPen) => {
            const pen = deepClone(aPen, true)
            const i = this.store.data.pens.findIndex((item) => item.id === pen.id)
            if (i > -1) {
              this.store.data.pens.splice(i, 1)
              this.store.pens[pen.id] = undefined
              if (!pen.calculative) {
                pen.calculative = {}
              }
              pen.calculative.canvas = this
            }
          })
          action.type = EditType.Delete
          break
        case EditType.Update:
          const pens = undo ? action.initPens : action.pens
          pens.forEach((p) => {
            const pen = deepClone(p, true)
            const i = this.store.data.pens.findIndex((item) => item.id === pen.id)
            if (i > -1) {
              pen.calculative = this.store.data.pens[i].calculative
              this.store.data.pens[i] = pen
              this.store.pens[pen.id] = pen
              pen.calculative.image = undefined
              this.updatePenRect(pen)
            }
          })
          break
        case EditType.Delete:
          action.pens.forEach((aPen) => {
            const pen = deepClone(aPen, true)
            if (!pen.calculative) {
              pen.calculative = {}
            }
            this.store.data.pens.splice(pen.calculative?.layer, 0, pen)
            // 先放进去，pens 可能是子节点在前，而父节点在后
            this.store.pens[pen.id] = pen
            pen.calculative.canvas = this
          })
          action.pens.forEach((aPen) => {
            const pen = this.store.pens[aPen.id]
            this.updatePenRect(pen)
            pen.calculative.image = undefined
            if (pen.image) {
              loadImage(pen.image, img => {
                pen.calculative.img = img
                pen.calculative.image = pen.image
                this.renderDelay(100)
              })
            }
          })
          action.type = EditType.Add
          break
      }
      this.render()

      this.store.emitter.emit(undo ? 'undo' : 'redo', action)
    }

    this.makePen = function (pen) {
      if (!pen.id) {
        pen.id = s8()
      }
      this.store.data.pens.push(pen)
      this.store.pens[pen.id] = pen

      if (pen.type == 'text') {
        if (!pen.fontSize) {
          pen.fontSize = 12
        }
        if (!pen.lineHeight) {
          pen.lineHeight = 1.5
        }
        if (!pen.fontFamily) {
          pen.fontFamily = 'Arial'
        }
        if (!pen.color) {
          pen.color = 'black'
        }
      }
      !pen.rotate && (pen.rotate = 0)
      pen.calculative = {canvas: this}
      for (const key of Object.keys(pen)) {
        if (typeof pen[key] !== 'object') {
          pen.calculative[key] = pen[key]
        }
      }

      this.updatePenRect(pen, false)

      if (pen.type == 'image') {
        pen.calculative.image = undefined
        loadImage(pen.image, img => {
          pen.calculative.image = pen.image
          pen.calculative.img = img
          this.renderDelay(100)
        })
      }
    }

    this.renderDelay = function (time = 100) {
      this.delayTimer && clearTimeout(this.delayTimer)
      this.delayTimer = setTimeout(() => {
        // 加载完图片，重新渲染一次
        this.render()
      }, time)
    }

    this.updateValue = function(pen, data) {
      Object.assign(pen, data)
      if (pen.type == 'text') {
        if (!pen.fontSize) {
          pen.fontSize = 12
        }
        if (!pen.lineHeight) {
          pen.lineHeight = 1.5
        }
        if (!pen.fontFamily) {
          pen.fontFamily = 'Arial'
        }
        if (!pen.color) {
          pen.color = 'black'
        }
      }
      !pen.rotate && (pen.rotate = 0)
      pen.calculative = {canvas: this}
      for (const key of Object.keys(pen)) {
        if (typeof pen[key] !== 'object') {
          pen.calculative[key] = pen[key]
        }
      }

      this.updatePenRect(pen, false)

      if (pen.type == 'image') {
        pen.calculative.image = undefined
        loadImage(pen.image, img => {
          pen.calculative.image = pen.image
          pen.calculative.img = img
          this.renderDelay(100)
        })
      }
  }

    this.updatePenRect = function (pen, worldRectIsReady) {
      if (worldRectIsReady) {
        calcPenRect(pen)
      } else {
        calcWorldRects(pen)
      }
      if (pen.type == 'text') {
        const scale = this.store.data.scale
        pen.calculative.fontSize = pen.fontSize * scale
      }
    }

    /** 计算节点世界坐标 */
    function calcWorldRects(pen) {
      let rect = {
        x: pen.x,
        y: pen.y,
        rotate: pen.rotate || 0
      };

      let calc = pen.calculative
      if (pen.type == 'text') {
        let {fontSize, fontFamily, lineHeight} = pen
        let ctx = calc.canvas.canvas.getContext('2d')
        let textLines = calc.text ? calc.text.split(/[\n\r]/) : []
        Object.assign(rect, getTextAreaSize(ctx, textLines, fontSize, fontFamily, lineHeight))
        calcCenter(rect)
        calc.textLines = textLines
      } else {
        rect.width = pen.width
        rect.height = pen.height
      }

      scaleRect(rect, calc.canvas.store.data.scale, {x: 0, y: 0})
      calcRightBottom(rect);
      calcCenter(rect);
      pen.calculative.worldRect = rect;
      return rect;
    }

    /** 将世界坐标返回给数据 */
    function calcPenRect(pen) {
      const worldRect = pen.calculative.worldRect;
      const scale = pen.calculative.canvas.store.data.scale

      pen.x = worldRect.x / scale
      pen.y = worldRect.y / scale
      pen.rotate = worldRect.rotate

      if (pen.type == 'image') {
        pen.width = worldRect.width / scale
        pen.height = worldRect.height / scale
      }
    }

    function getFont(
      fontStyle = 'normal',
      textDecoration = 'normal',
      fontWeight = 'normal',
      fontSize = 12,
      fontFamily = 'Arial',
      lineHeight = 1
    ) {
      return `${fontStyle} ${textDecoration} ${fontWeight} ${fontSize}px/${lineHeight} ${fontFamily}`;
    }

    /** 获取文本宽高 */
    function getTextAreaSize(ctx, textLines, fontSize, fontFamily, lineHeight) {
      ctx.font = getFont('normal', 'normal', 'normal', fontSize, fontFamily, lineHeight)
      let maxWidth = 0
      textLines.forEach((text) => {
        const width = ctx.measureText(text).width;
        maxWidth < width && (maxWidth = width);
      })
      return {
        width: maxWidth,
        height: textLines.length * fontSize * lineHeight
      }
    }

    this.resize = function (w, h) {
      w = w || this.store.data.width
      h = h || this.store.data.height

      this.clientRect = this.externalElements.getBoundingClientRect()

      let width = this.clientRect.width
      let height = this.clientRect.height

      let scale = width / w
      if (scale * h > height) {
        scale = height / h
      }
      let showWidth = scale * w
      let showHeight = scale * h

      this.scale(scale)
      this.store.data.x = (width - showWidth) / 2
      this.store.data.y = (height - showHeight) / 2

      width = (width * this.store.dpiRatio) | 0
      height = (height * this.store.dpiRatio) | 0
      this.canvas.width = width
      this.canvas.height = height
      this.canvas.getContext('2d').scale(this.store.dpiRatio, this.store.dpiRatio)

      this.render()
    }

    this.patchFlags = false
    this.render = function (patchFlags) {
      let now
      if (patchFlags == null || patchFlags === true || patchFlags === Infinity) {
        now = performance.now()
        this.patchFlags = true
      } else if (patchFlags > 1) {
        now = patchFlags
      } else {
        now = performance.now()
      }
      if (!this.patchFlags) {
        return
      }

      if (now - this.lastRender < 30) {
        if (this.renderTimer) {
          cancelAnimationFrame(this.renderTimer)
        }
        this.renderTimer = requestAnimationFrame(this.render)
        return
      }
      this.renderTimer = undefined
      this.lastRender = now

      const ctx = this.canvas.getContext('2d')
      ctx.textBaseline = 'middle'
      ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
      ctx.save()
      ctx.translate(this.store.data.x, this.store.data.y)
      ctx.fillStyle = '#fff'
      ctx.rect(0, 0, this.store.data.width * this.store.data.scale, this.store.data.height * this.store.data.scale)
      ctx.fill()
      ctx.clip()
      if (this.store.backgroundImg) {
        ctx.drawImage(this.store.backgroundImg, 0, 0, this.store.data.width * this.store.data.scale, this.store.data.height * this.store.data.scale)
      }
      this.renderPens()
      ctx.restore()
      ctx.save()
      ctx.translate(this.store.data.x, this.store.data.y)
      this.renderBorder()
      this.renderHoverPoint()
      ctx.restore()
    }

    /** 渲染所有节点 */
    this.renderPens = function () {
      const ctx = this.canvas.getContext('2d')
      for (const pen of this.store.data.pens) {
        if (!isFinite(pen.x)) {
          continue
        }
        renderPen(ctx, pen)
      }
    }

    /** 渲染节点 */
    function renderPen(ctx, pen) {
      ctx.save()
      ctx.translate(0.5, 0.5)
      ctx.beginPath()

      setFlip(ctx, pen)
      setRotate(ctx, pen)
      setGlobalAlpha(ctx, pen)
      let worldRect = pen.calculative.worldRect
      if (pen.calculative.moving) {
        ctx.globalAlpha = 0.7
      }

      if (pen.type == 'image') {
        if (pen.calculative.img && pen.calculative.image) {
          ctx.drawImage(pen.calculative.img, worldRect.x, worldRect.y, worldRect.width, worldRect.height)
        }
      }
      if (pen.type == 'text') {
        let {color, fontSize, fontFamily, lineHeight} = pen.calculative
        ctx.fillStyle = color
        ctx.font = getFont('normal', 'normal', 'normal', fontSize, fontFamily, lineHeight)
        pen.calculative.textLines.forEach((line, i) => {
          ctx.fillText(line, worldRect.x, worldRect.y + (lineHeight * fontSize * i) + (lineHeight * fontSize / 2))
        })
      }
      ctx.restore()
    }

    /** 旋转 */
    function setRotate(ctx, pen, png = false) {
      if (png) {
        if (pen.rotate) {
          const {x, y} = pen.center
          ctx.translate(x, y)
          let rotate = (pen.rotate * Math.PI) / 180
          ctx.rotate(rotate)
          ctx.translate(-x, -y)
        }
      } else {
        if (pen.calculative.worldRect.rotate) {
          const {x, y} = pen.calculative.worldRect.center
          ctx.translate(x, y)
          let rotate = (pen.calculative.worldRect.rotate * Math.PI) / 180
          ctx.rotate(rotate)
          ctx.translate(-x, -y)
        }
      }
    }

    /** 翻转 */
    function setFlip(ctx, pen, png = false) {
      if (png) {
        const {x, ex, y, ey} = pen || {}
        if (pen.flipX) {
          ctx.translate(x + ex + 0.5, 0.5)
          ctx.scale(-1, 1)
        }
        if (pen.flipY) {
          ctx.translate(0.5, y + ey + 0.5)
          ctx.scale(1, -1)
        }
      } else {
        const {x, ex, y, ey} = pen.calculative.worldRect || {}
        if (pen.calculative.flipX) {
          ctx.translate(x + ex + 0.5, 0.5)
          ctx.scale(-1, 1)
        }
        if (pen.calculative.flipY) {
          ctx.translate(0.5, y + ey + 0.5)
          ctx.scale(1, -1)
        }
      }
    }

    /** 更改 ctx 的 globalAlpha 属性 */
    function setGlobalAlpha(ctx, pen, png = false) {
      if (png) {
        const globalAlpha = pen.globalAlpha
        if (globalAlpha < 1) {
          ctx.globalAlpha = globalAlpha
        }
      } else {
        const globalAlpha = pen.calculative.globalAlpha
        if (globalAlpha < 1) {
          ctx.globalAlpha = globalAlpha
        }
      }
    }

    /** 渲染边框 */
    this.renderBorder = function () {
      let activeRect = this.activeRect
      //绘制选中状态的边框
      if (activeRect) {
        const ctx = this.canvas.getContext('2d');
        ctx.save();
        ctx.translate(0.5, 0.5);
        if (activeRect.rotate) {
          ctx.translate(activeRect.center.x, activeRect.center.y);
          ctx.rotate((activeRect.rotate * Math.PI) / 180);
          ctx.translate(-activeRect.center.x, -activeRect.center.y);
        }
        ctx.strokeStyle = '#3F5BE0';

        // 绘制选中框
        ctx.lineWidth = 1.5
        ctx.beginPath();
        ctx.strokeRect(
          activeRect.x,
          activeRect.y,
          activeRect.width,
          activeRect.height
        );

        ctx.globalAlpha = 1;
        // 如果节点锁定
        if (getPensLock(this.store.active)) {
          ctx.restore();
          return;
        }

        // 绘制旋转控制线
        ctx.lineWidth = 1
        ctx.setLineDash([1])
        ctx.beginPath();
        ctx.moveTo(activeRect.center.x, activeRect.y);
        ctx.lineTo(activeRect.center.x, activeRect.y - 30);
        ctx.stroke();

        // 绘制旋转控制点
        if (this.rotateImg) {
          ctx.drawImage(this.rotateImg, activeRect.center.x - 8, activeRect.y - 46, 18, 18)
        }

        ctx.restore();
      }
    }

    /** 渲染控制点 */
    this.renderHoverPoint = function () {
      const ctx = this.canvas.getContext('2d');
      ctx.save();
      ctx.translate(0.5, 0.5);

      let {activeRect, sizeControlPoints} = this

      if (activeRect) {
        if (this.store.active.length == 1) {
          let pen = this.store.active[0]
          ctx.strokeStyle = '#3F5BE0';
          ctx.fillStyle = '#ffffff';
          let locked = pen.locked
          if (pen.type == 'image') {
            sizeControlPoints.forEach((pt, i) => {
              if (activeRect.rotate) {
                ctx.save();
                ctx.translate(pt.x, pt.y);
                ctx.rotate((activeRect.rotate * Math.PI) / 180);
                ctx.translate(-pt.x, -pt.y);
              }
              if (i == 0) {
                if (this.closeImg) {
                  ctx.drawImage(this.closeImg, pt.x - 8, pt.y - 8, 18, 18)
                }
              } else if (i == 2 && !locked) {
                if (this.resizeImg) {
                  ctx.drawImage(this.resizeImg, pt.x - 10, pt.y - 10, 18, 18)
                }
              } else if (i == 3 && !locked) {
                if (this.copyImg) {
                  ctx.drawImage(this.copyImg, pt.x - 10, pt.y - 10, 18, 18)
                }
              } else if (i > 3 && !locked) {
                if (i % 2 == 0) {
                  drawRoundRect(ctx, pt.x - 12, pt.y - 4.5, 24, 9, 4.5)
                } else {
                  drawRoundRect(ctx, pt.x - 4.5, pt.y - 12, 9, 24, 4.5)
                }
              }
              if (activeRect.rotate) {
                ctx.restore();
              }
            })
          }
          if (pen.type == 'text') {
            let pt = sizeControlPoints[1]
            if (activeRect.rotate) {
              ctx.save();
              ctx.translate(pt.x, pt.y);
              ctx.rotate((activeRect.rotate * Math.PI) / 180);
              ctx.translate(-pt.x, -pt.y);
            }
            if (this.closeImg) {
              ctx.drawImage(this.closeImg, pt.x - 8, pt.y - 8, 18, 18)
            }
            if (activeRect.rotate) {
              ctx.restore();
            }
          }
        }
      }

      ctx.restore();
    }

    /** 绘制圆角矩形 */
    function drawRoundRect(ctx, x, y, width, height, r) {
      ctx.beginPath();
      ctx.moveTo(x + r, y);
      ctx.arcTo(x + width, y, x + width, y + height, r);
      ctx.arcTo(x + width, y + height, x, y + height, r);
      ctx.arcTo(x, y + height, x, y, r);
      ctx.arcTo(x, y, x + width, y, r);
      ctx.closePath();
      ctx.fill();
      ctx.stroke();
    }

    /** 清空画布 */
    this.clear = function () {
      this.activeRect = undefined
      this.sizeControlPoints = undefined
      this.canvas.getContext('2d').clearRect(0, 0, this.canvas.width, this.canvas.height)
    }

    /** 创建输入框 */
    this.createInput = function () {
      this.inputParent.classList.add('image-designer-input');
      this.input.classList.add('input-div');
      this.inputParent.appendChild(this.input);
      this.externalElements.appendChild(this.inputParent);

      let sheet;
      for (let i = 0; i < document.styleSheets.length; i++) {
        if (document.styleSheets[i].title === 'image-designer') {
          sheet = document.styleSheets[i];
        }
      }
      if (!sheet) {
        const style = document.createElement('style');
        style.title = 'image-designer';
        document.head.appendChild(style);
        sheet = style.sheet;
        sheet.insertRule('.image-designer-input{display:none;position:absolute;outline:none;}');
        sheet.insertRule('.image-designer-input .input-div{resize:none;border:none;outline:none;background:transparent;width: 100%;height:100%;padding: 0}');
      }

      this.input.onclick = (e) => {
        e.stopPropagation();
        const pen = this.store.pens[this.input.dataset.penId];
        this.store.emitter.emit('clickInput', pen);
      }
      this.input.onkeyup = (e) => {
        e.stopPropagation();
        const pen = this.store.pens[this.input.dataset.penId]
        this.store.emitter.emit('input', {pen, text: e.key})
        pen.calculative.text = this.input.value

        this.updatePenRect(pen)
        const scale = this.store.data.scale
        let {fontSize} = pen
        const textRect = pen.calculative.worldRect
        setStyle(this.inputParent, {
          width: textRect.width + fontSize * scale * 2 + 'px',
          height: textRect.height + fontSize * scale * 2 + 'px',
        })
        this.calcActiveRect()
        this.renderDelay(100)
      };
      this.input.onkeydown = (e) => {
        e.stopPropagation();
      };
      this.input.onmousedown = (e) => {
        e.stopPropagation();
      }
      this.input.onwheel = (e) => {
        e.stopPropagation();
      }
      this.input.onpaste = (e) => {
        e.preventDefault();
        let text = '';
        if (e.clipboardData && e.clipboardData.getData) {
          text = e.clipboardData.getData('text/plain');
        }
        this.input.value += text
      };
    }

    /** 显示输入框 */
    this.showInput = function (pen, rect, background = 'transparent') {
      if (
        !globalThis ||
        !this.store.hover ||
        this.store.hover.locked
      ) {
        return;
      }

      if (this.input.dataset.penId === pen.id) {
        this.input.focus();
        return;
      }

      const scale = this.store.data.scale;
      let {color, fontSize, lineHeight, fontStyle, fontFamily, fontWeight} = pen
      const textRect = rect || pen.calculative.worldRect;

      this.input.value = pen.calculative.text + '' || '';
      setStyle(this.inputParent, {
        left: textRect.x + this.store.data.x + 'px',
        top: textRect.y + this.store.data.y + 'px',
        width: textRect.width + fontSize * scale + 'px',
        height: textRect.height + fontSize * scale + 'px',
        zIndex: '9999',
        background: background,
        transform: pen.rotate % 360 ? `rotate(${pen.rotate}deg)` : null,
        display: 'block'
      })
      let font_scale = 1;
      if (fontSize < 12) {
        font_scale = 12 / fontSize;
      }
      let styleObj = {
        fontFamily: fontFamily || null,
        color: color || 'black',
        fontStyle: fontStyle || 'normal',
        fontWeight: fontWeight || 'normal',
        lineHeight: (fontSize * scale > 12 ? fontSize * lineHeight * scale : fontSize * lineHeight * font_scale) + 'px'
      }
      if (fontSize * scale < 12) {
        styleObj.fontSize = fontSize + 'px'
        styleObj.zoom = scale
      } else {
        styleObj.fontSize = fontSize * scale + 'px'
        styleObj.zoom = 1
      }
      setStyle(this.input, styleObj)
      this.input.dataset.penId = pen.id;
      this.input.focus();
      this.render();
    }

    /** 隐藏输入框 */
    this.hideInput = () => {
      if (this.inputParent.style.display === 'block') {
        this.inputParent.style.display = 'none';
        const pen = this.store.pens[this.input.dataset.penId];
        if (!pen) {
          return;
        }
        this.input.dataset.value = this.input.value;
        if (pen.text !== this.input.dataset.value) {
          const initPens = [deepClone(pen, true)];
          pen.text = this.input.dataset.value;
          this.input.dataset.penId = undefined;
          this.patchFlags = true;
          this.pushHistory({
            type: EditType.Update,
            pens: [deepClone(pen, true)],
            initPens,
          });
          this.store.emitter.emit('valueUpdate', pen);
        }
      }
      this.input.dataset.penId = undefined;
      this.render();
    }

    /** 画布平移 */
    this.translate = function (x = 0, y = 0) {
      this.store.data.x += x * this.store.data.scale
      this.store.data.y += y * this.store.data.scale
      this.store.data.x = Math.round(this.store.data.x)
      this.store.data.y = Math.round(this.store.data.y)
      setTimeout(() => {
        this.render();
      });
      this.store.emitter.emit('translate', {
        x: this.store.data.x,
        y: this.store.data.y,
      });
    }

    /**
     * 缩放整个画布
     * @param scale 缩放比例，最终的 scale
     * @param center 中心点，引用类型，存在副作用，会更改原值
     */
    this.scale = function (scale, center = {x: 0, y: 0}) {
      const minScale = 0.1
      const maxScale = 10
      if (!(scale >= minScale && scale <= maxScale)) {
        return;
      }

      this.calibrateMouse(center);
      this.store.data.scale = scale;

      this.store.data.pens.forEach((pen) => {
        this.updatePenRect(pen);
      });
      this.calcActiveRect();
      setTimeout(() => {
        this.render();
        this.store.emitter.emit('scale', this.store.data.scale);
      });
    }

    /** 旋转 */
    this.rotatePens = function (e) {
      if (!this.rotatingPens) {
        this.rotatingPens = deepClone(this.store.active)
      }

      this.activeRect.rotate = calcRotate(e, this.activeRect.center)
      if (this.activeRect.rotate % 90 < 10) {
        this.activeRect.rotate -= this.activeRect.rotate % 90
      }
      if (this.activeRect.rotate % 90 > 80) {
        this.activeRect.rotate += 90 - (this.activeRect.rotate % 90)
      }
      if (this.store.active.length === 1) {
        this.lastRotate = this.store.active[0].calculative.worldRect.rotate || 0
      }
      const angle = this.activeRect.rotate - this.lastRotate
      for (const pen of this.store.active) {
        if (pen.calculative.rotate) {
          pen.calculative.rotate += angle
        } else {
          pen.calculative.rotate = angle
        }
        rotatePoint(pen.calculative.worldRect.center, angle, this.activeRect.center)
        pen.calculative.x = pen.calculative.worldRect.center.x - pen.calculative.worldRect.width / 2
        pen.calculative.y = pen.calculative.worldRect.center.y - pen.calculative.worldRect.height / 2
        pen.calculative.worldRect.x = pen.calculative.x
        pen.calculative.worldRect.y = pen.calculative.y
        pen.calculative.worldRect.rotate = pen.calculative.rotate
      }
      this.lastRotate = this.activeRect.rotate
      this.getSizeControlPoints()
      this.render()
    }

    /** 旋转结束，更新到pens */
    this.rotatedActivePens = function () {
      const initPens = this.rotatingPens

      this.store.active.forEach(pen => {
        this.updatePenRect(pen, true)
      })
      const pens = deepClone(this.store.active, true)
      this.pushHistory({
        type: EditType.Update,
        pens,
        initPens,
      })
      this.store.emitter.emit('rotatePens', pens)
      this.rotatingPens = undefined
    }

    /** 缩放节点 */
    this.resizePens = function (e, ratio) {
      if (!this.resizingPens) {
        this.resizingPens = deepClone(this.store.active, true);
      }

      if (!this.initActiveRect) {
        this.initActiveRect = deepClone(this.activeRect);
        return;
      }

      const p1 = {x: this.mouseDown.x, y: this.mouseDown.y};
      const p2 = {x: e.x, y: e.y};
      // rotatePoint(p1, -this.activeRect.rotate, this.activeRect.center);
      // rotatePoint(p2, -this.activeRect.rotate, this.activeRect.center);

      let x = p2.x - p1.x;
      let y = p2.y - p1.y;

      const w = this.activeRect.width;
      const h = this.activeRect.height;
      let offsetX = x - this.lastOffsetX;
      let offsetY = y - this.lastOffsetY;
      this.lastOffsetX = x;
      this.lastOffsetY = y;
      if (e.ctrlKey || (this.resizingPens.length === 1 && this.resizingPens[0].ratio) || ratio) {
        const sign = [1, 3].includes(this.resizeIndex) ? -1 : 1;
        offsetY = (sign * (offsetX * h)) / w;
        //TODO 节点在旋转后等比缩放失效，看看算法有什么问题
      }
      resizeRect(this.activeRect, offsetX, offsetY, this.resizeIndex);
      calcCenter(this.activeRect);

      const scaleX = this.activeRect.width / w;
      const scaleY = this.activeRect.height / h;
      this.store.active.forEach((pen, i) => {
        pen.calculative.worldRect.x = this.activeInitPos[i].x * this.activeRect.width + this.activeRect.x;
        pen.calculative.worldRect.y = this.activeInitPos[i].y * this.activeRect.height + this.activeRect.y;
        pen.calculative.worldRect.width *= scaleX;
        pen.calculative.worldRect.height *= scaleY;
        calcRightBottom(pen.calculative.worldRect);
        calcCenter(pen.calculative.worldRect);
      });
      this.getSizeControlPoints();
      this.render();
    }

    /** 拖拽结束，更新到pens */
    this.resizedActivePens = function () {
      const initPens = this.resizingPens

      this.store.active.forEach(pen => {
        this.updatePenRect(pen, true)
      })
      const pens = deepClone(this.store.active, true)

      this.pushHistory({
        type: EditType.Update,
        pens,
        initPens,
      })
      this.store.emitter.emit('resizePens', pens)
    }

    /** 移动节点 */
    this.movePens = function (e) {
      if (!this.activeRect) {
        return
      }

      if (!this.initActiveRect) {
        this.initActiveRect = deepClone(this.activeRect);
        return;
      }

      if (!this.mouseDown) {
        return
      }

      if (!this.store.active.length) {
        return
      }

      let pens = this.store.active
      let hasLocked = pens.some(item => !!item.locked)
      if (hasLocked) {
        return
      }

      if (!this.movingPens) {
        this.movingPens = deepClone(pens, true)
      }

      let x = e.x - this.mouseDown.x
      let y = e.y - this.mouseDown.y
      e.shiftKey && !e.ctrlKey && (y = 0)
      e.ctrlKey && (x = 0)

      const rect = deepClone(this.initActiveRect)
      translateRect(rect, x, y)
      const offset = {
        x: rect.x - this.activeRect.x,
        y: rect.y - this.activeRect.y,
      }

      pens.forEach((pen) => {
        translateRect(pen.calculative.worldRect, offset.x, offset.y)
        pen.calculative.x = pen.calculative.worldRect.x
        pen.calculative.y = pen.calculative.worldRect.y
      })

      translateRect(this.activeRect, offset.x, offset.y)
      this.getSizeControlPoints()
      this.render()
    }

    /**
     * 拖拽结束，数据更新到 active.pens
     */
    this.movedActivePens = function () {
      const initPens = this.movingPens

      this.store.active.forEach(pen => {
        this.updatePenRect(pen, true)
      })
      const pens = deepClone(this.store.active, true)

      this.pushHistory({
        type: EditType.Update,
        pens,
        initPens,
      })
      this.store.emitter.emit('translatePens', pens)
    }

    /**
     * 移动 节点们
     * @param pens 节点们
     * @param x 偏移 x
     * @param y 偏移 y
     */
    this.translatePens = function (pens = this.store.active, x, y) {
      if (!pens || !pens.length) {
        return
      }
      let hasLocked = pens.some((item) => item.locked)
      if (hasLocked) {
        return
      }
      const initPens = deepClone(pens, true)
      this.activeRect && translateRect(this.activeRect, x, y)

      pens.forEach((pen) => {
        if (pen.locked) {
          return
        }
        translateRect(pen.calculative.worldRect, x, y)
        this.updatePenRect(pen, true)
        pen.calculative.x = pen.x
        pen.calculative.y = pen.y
      })
      this.activeRect && this.getSizeControlPoints()
      this.render()

      this.pushHistory({
        type: EditType.Update,
        pens: deepClone(pens, true),
        initPens,
      })
      this.store.emitter.emit('translatePens', pens)
    }

    /** 取消选中 */
    this.inactive = function (drawing) {
      if (!this.store.active.length) {
        return;
      }
      this.store.active.forEach((pen) => {
        pen.calculative.active = undefined;
      });
      !drawing && this.store.emitter.emit('inactive', this.store.active);
      this.store.active = [];
      this.activeRect = undefined;
      this.sizeControlPoints = undefined;
      this.patchFlags = true;
    }

    /** 选中 */
    this.active = function (pens, emit = true) {
      if (this.store.active) {
        for (const pen of this.store.active) {
          pen.calculative.active = undefined;
        }
      }
      this.store.active = [];
      pens.forEach((pen) => {
        pen.calculative.active = true;
        this.store.active.push(pen);
      });
      this.activeRect = undefined;
      this.calcActiveRect();
      this.patchFlags = true;
      emit && this.store.emitter.emit('active', this.store.active);
    }

    /** 清除鼠标悬浮 */
    this.clearHover = function () {
      this.hoverType = HoverType.None
      this.store.hover = null
    }

    /** 删除节点 */
    this.delete = function (pens = this.store.active, canDelLocked = false, history = true) {
      if (!pens || !pens.length) {
        return;
      }
      if (!canDelLocked) {
        pens = pens.filter((pen) => !pen.locked);
      }
      if (!pens || !pens.length) {
        return;
      }
      const deletePens = [];
      pens.forEach((pen) => {
        const i = this.store.data.pens.findIndex((item) => item.id === pen.id);
        if (i > -1) {
          const delPen = this.store.pens[pen.id];
          delPen.calculative.active = undefined;
          deletePens.push(delPen);
          this.store.data.pens.splice(i, 1);
          this.store.pens[pen.id] = undefined;
          delete this.store.pens[pen.id];
        }
      })
      this.inactive();
      this.clearHover();
      this.render();
      if (history) {
        this.pushHistory({type: EditType.Delete, pens: deletePens});
      }
      this.store.emitter.emit('delete', pens);
    }

    /** 剪切板名称 */
    this.clipboardName = 'image-designer'

    /** 复制 */
    this.copy = function (pens, emit = true) {
      const page = s8();
      const scale = this.store.data.scale
      this.store.clipboard = undefined
      localStorage.removeItem(this.clipboardName)
      sessionStorage.setItem('page', page)

      let copyPens = deepClone(pens || this.store.active, false)
      //根据pens顺序复制
      copyPens.forEach((activePen) => {
        activePen.copyIndex = this.store.data.pens.findIndex((pen) => pen.id === activePen.id)
      })
      copyPens.sort((a, b) => {
        return a.copyIndex - b.copyIndex;
      })
      copyPens.forEach((activePen) => {
        delete activePen.copyIndex;
      })

      const clipboard = {
        imageDesigner: true,
        pens: copyPens,
        scale,
        page,
        initRect: deepClone(this.activeRect),
        offset: 10
      }

      if (
        navigator.clipboard &&
        !navigator.userAgent.includes('Firefox')
      ) {
        try {
          navigator.clipboard.writeText(JSON.stringify(clipboard));
        } catch {
          localStorage.setItem(this.clipboardName, JSON.stringify(clipboard));
        }
      } else {
        localStorage.setItem(this.clipboardName, JSON.stringify(clipboard));
      }

      emit && this.store.emitter.emit('copy', clipboard.pens);
    }

    /** 剪切 */
    this.cut = function (pens) {
      this.copy(pens, false);
      this.delete(pens);
      this.store.emitter.emit('cut', pens);
    }

    /** 粘贴 */
    this.paste = function () {
      let clipboardText;
      let clipboard;

      const doNext = () => {
        if (clipboardText) {
          try {
            clipboard = JSON.parse(clipboardText);
          } catch (e) {
            console.warn('剪切板数据不是json', e.message);
            return;
          }
          if (!clipboard || !clipboard.imageDesigner) {
            return;
          }
        } else {
          return;
        }

        let offset;
        if (this.store.clipboard) {
          offset = this.store.clipboard.offset + 10;
        }
        this.store.clipboard = deepClone(clipboard);

        const curPage = sessionStorage.getItem('page');
        if (curPage !== clipboard.page) {
          this.store.clipboard.offset = 0;
        } else {
          offset && (this.store.clipboard.offset = offset);
        }

        const rootPens = this.store.clipboard.pens
        for (const pen of rootPens) {
          this.pastePen(pen);
        }
        sessionStorage.setItem('page', clipboard.page);
        this.active(rootPens);
        this.pushHistory({type: EditType.Add, pens: this.store.clipboard.pens});
        this.render();
        this.store.emitter.emit('add', this.store.clipboard.pens);
        this.store.emitter.emit('paste', this.store.clipboard.pens);
      }

      if (
        navigator.clipboard &&
        !navigator.userAgent.includes('Firefox')
      ) {
        try {
          navigator.clipboard.readText().then(res => {
            clipboardText = res
            doNext()
          })
        } catch {
          clipboardText = localStorage.getItem(this.clipboardName);
          doNext()
        }
      } else {
        clipboardText = localStorage.getItem(this.clipboardName);
        doNext()
      }
    }

    /**
     * 粘贴节点
     * @param pen 当前复制的节点
     * @returns 复制后的节点
     */
    this.pastePen = function (pen) {
      pen.id = s8()
      pen.x += this.store.clipboard.offset * this.store.data.scale
      pen.y += this.store.clipboard.offset * this.store.data.scale
      this.makePen(pen)
      return pen
    }

    /** 从缓存新增节点 */
    this.insertPensFromCache = function (pens, e) {
      for (const pen of pens) {
        if (!pen.id) {
          pen.id = s8();
        }
        !pen.calculative && (pen.calculative = {canvas: this})
        this.store.pens[pen.id] = pen;
      }
      let scale = this.store.data.scale
      for (const pen of pens) {
        if (pen.width && pen.height) {
          pen.x = e.x - pen.width / 2
          pen.y = e.y - pen.height / 2
        } else {
          pen.x = e.x / scale
          pen.y = e.y / scale
        }
      }

      this.addPens(pens, true)
      this.active(pens)
      this.render()
      this.externalElements.focus()
    }

    /** 添加节点 */
    this.addPens = function (pens, history) {
      const list = [];
      for (const pen of pens) {
        this.makePen(pen);
        list.push(pen);
      }
      this.render();
      this.store.emitter.emit('add', list);
      if (history) {
        this.pushHistory({type: EditType.Add, pens: deepClone(list, true)});
      }
      return list;
    }

    /** 导出图片 */
    this.toPng = function (callback) {
      let width = this.store.data.width
      let height = this.store.data.height

      const canvas = document.createElement('canvas');
      canvas.width = width * this.store.dpiRatio;
      canvas.height = height * this.store.dpiRatio;
      if (canvas.width > 32767 || canvas.height > 32767 || (!navigator.userAgent.includes('Firefox') && canvas.height * canvas.width > 268435456) || (navigator.userAgent.includes('Firefox') && canvas.height * canvas.width > 472907776)) {
        throw new Error('can not to png, because the size exceeds the browser limit');
      }
      const ctx = canvas.getContext('2d')
      ctx.scale(this.store.dpiRatio, this.store.dpiRatio)
      ctx.textBaseline = 'middle'
      ctx.drawImage(this.store.backgroundImg, 0, 0, width, height)
      for (let pen of this.store.data.pens) {
        pen = deepClone(pen, false)
        ctx.save()
        ctx.translate(0.5, 0.5)
        ctx.beginPath()

        if (pen.type == 'text') {
          let {fontSize, fontFamily, lineHeight} = pen
          let textLines = pen.text ? pen.text.split(/[\n\r]/) : []
          Object.assign(pen, getTextAreaSize(ctx, textLines, fontSize, fontFamily, lineHeight))
        }
        calcCenter(pen)
        calcRightBottom(pen)

        setFlip(ctx, pen, true)
        setRotate(ctx, pen, true)
        setGlobalAlpha(ctx, pen, true)

        if (pen.type == 'image') {
          if (pen.calculative.img && pen.calculative.image) {
            ctx.drawImage(pen.calculative.img, rect.x, rect.y, rect.width, rect.height)
          }
        }
        if (pen.type == 'text') {
          let {color, fontSize, fontFamily, lineHeight} = pen
          ctx.fillStyle = color
          ctx.font = getFont('normal', 'normal', 'normal', fontSize, fontFamily, lineHeight)
          pen.calculative.textLines.forEach((line, i) => {
            ctx.fillText(line, rect.x, rect.y + (lineHeight * fontSize * i) + (lineHeight * fontSize / 2))
          })
        }
        ctx.restore()
      }
      if (callback) {
        canvas.toBlob(callback);
        return;
      }
      return canvas.toDataURL();
    }

    /** 计算高亮矩形 */
    this.calcActiveRect = function () {
      if (this.store.active.length == 1) {
        this.activeRect = deepClone(this.store.active[0].calculative.worldRect)
        this.activeRect.rotate = this.activeRect.rotate || 0
        calcCenter(this.activeRect)
      } else {
        const canMovePens = this.store.active.filter(pen => !pen.locked)
        if (!canMovePens.length) {
          return
        } else {
          this.activeRect = getRect(canMovePens)
          this.activeRect.rotate = 0
        }
      }
      this.lastRotate = 0
      this.getSizeControlPoints()
    }

    /** 获取尺寸控制点 */
    this.getSizeControlPoints = function () {
      this.sizeControlPoints = rectToPoints(this.activeRect)
      // 正上 正右 正下 正左
      const pts = [
        {x: 0.5, y: 0},
        {x: 1, y: 0.5},
        {x: 0.5, y: 1},
        {x: 0, y: 0.5},
      ]
      const x = this.activeRect.x
      const y = this.activeRect.y
      const width = this.activeRect.width
      const height = this.activeRect.height
      const rotate = this.activeRect.rotate
      const center = this.activeRect.center
      pts.forEach((pt) => {
        const p = {
          x: pt.x * width + x,
          y: pt.y * height + y,
        }
        rotatePoint(p, rotate, center)
        this.sizeControlPoints.push(p)
      })
    }

    /** 校准鼠标坐标 */
    this.calibrateMouse = function (pt) {
      pt.x -= this.store.data.x
      pt.y -= this.store.data.y
      return pt
    }

    /** 点大小 */
    this.pointSize = 8

    /** 获取鼠标悬浮内容 */
    this.getHover = function (point) {
      let hoverType = HoverType.None
      this.store.hover = undefined
      if (this.activeRect) {
        if (this.store.active.length == 1) {
          let pen = this.store.active[0]
          if (!pen.locked) {
            const rotatePt = {
              x: this.activeRect.center.x,
              y: this.activeRect.y - 35,
            }
            if (this.activeRect.rotate) {
              rotatePoint(rotatePt, this.activeRect.rotate, this.activeRect.center)
            }
            // 旋转控制点
            if (hitPoint(point, rotatePt, 12)) {
              hoverType = HoverType.Rotate
              this.externalElements.style.cursor = 'pointer'
            }
          }
          // 大小控制点 删除 复制
          if (pen.type == 'image') {
            for (let i = 0; i < 8; i++) {
              if (i == 1) {
                continue
              }
              if (hitPoint(point, this.sizeControlPoints[i], this.pointSize)) {
                if (i == 0) {
                  hoverType = HoverType.Remove
                } else if (i == 2) {
                  hoverType = HoverType.ResizeByRatio
                } else if (i == 3) {
                  hoverType = HoverType.Copy
                } else {
                  hoverType = HoverType.Resize
                }
                this.resizeIndex = i
                this.externalElements.style.cursor = 'pointer'
                break
              }
            }
          }
          if (pen.type == 'text') {
            if (hitPoint(point, this.sizeControlPoints[1], this.pointSize)) {
              hoverType = HoverType.Remove
              this.externalElements.style.cursor = 'pointer'
            }
          }
        } else {
          if (!this.store.active.some(pen => !!pen.locked)) {
            const rotatePt = {
              x: this.activeRect.center.x,
              y: this.activeRect.y - 35,
            }
            if (this.activeRect.rotate) {
              rotatePoint(rotatePt, this.activeRect.rotate, this.activeRect.center)
            }
            // 旋转控制点
            if (hitPoint(point, rotatePt, 12)) {
              hoverType = HoverType.Rotate
              this.externalElements.style.cursor = 'pointer'
            }
          }
        }
      }
      if (hoverType === HoverType.None) {
        hoverType = this.inPens(point, this.store.data.pens)
      }

      if (!hoverType && pointInRect(point, this.activeRect)) {
        hoverType = HoverType.Node
        this.externalElements.style.cursor = 'move'
      }
      this.hoverType = hoverType

      if (hoverType === HoverType.None) {
        if (!this.mouseDown) {
          this.externalElements.style.cursor = 'default';
        }
        this.store.hover = undefined;
      }

      if (this.store.lastHover !== this.store.hover) {
        this.canvas.patchFlags = true
        if (this.store.lastHover) {
          this.store.lastHover.calculative.hover = false
          this.store.emitter.emit('leave', this.store.lastHover)
        }
        if (this.store.hover) {
          this.store.hover.calculative.hover = true
          this.store.emitter.emit('enter', this.store.hover)
        }
        this.store.lastHover = this.store.hover
      }
    }

    /** 获取坐标点在节点中悬浮的内容 */
    this.inPens = function (point, pens) {
      let hoverType = HoverType.None
      for (let i = pens.length - 1; i >= 0; --i) {
        const pen = pens[i]
        const radius = getLineRadius(pen)
        if (
          !pen.calculative.active &&
          !pointInSimpleRect(point, pen.calculative.worldRect, radius) &&
          !pointInRect(point, pen.calculative.worldRect)
        ) {
          continue
        }

        let isIn = pointInRect(point, pen.calculative.worldRect)
        if (isIn) {
          if (!pen.locked) {
            this.externalElements.style.cursor = 'move'
          } else {
            this.externalElements.style.cursor = 'default'
          }

          this.store.hover = pen
          hoverType = HoverType.Node
          break
        }
      }
      return hoverType
    }

    /** 获取节点线框半径，用于判断鼠标悬浮 */
    function getLineRadius(pen) {
      return pen?.lineWidth ? pen.lineWidth / 2 + 4 : 4;
    }

    function getPensLock(pens) {
      for (let i = 0; i < pens.length; i++) {
        if (!pens[i].locked) {
          return false
        }
      }
      return true
    }

    /** 页面滚动 */
    this.handleScroll = () => {
      if (this.timer) {
        clearTimeout(this.timer);
      }
      this.timer = setTimeout(() => {
        this.clientRect = this.canvas.getBoundingClientRect();
        this.timer = undefined;
      }, 100);
    }

    /** 触摸开始 */
    this.handleTouchStart = (e) => {
      if (this.touchStartTimer) {
        clearTimeout(this.touchStartTimer)
      }
      this.touchStartTimer = setTimeout(() => {
        this.touchStart = performance.now()
        const x = e.touches[0].pageX - this.clientRect.x
        const y = e.touches[0].pageY - this.clientRect.y
        const pos = {x, y}
        this.calibrateMouse(pos)
        this.getHover(pos)
        this.handleMouseDown({
          x,
          y,
          clientX: e.touches[0].clientX,
          clientY: e.touches[0].clientY,
          pageX: e.touches[0].pageX,
          pageY: e.touches[0].pageY,
          ctrlKey: e.ctrlKey || e.metaKey,
          shiftKey: e.shiftKey,
          altKey: e.altKey,
          buttons: 1
        })

        //两指
        if (e.touches.length === 2) {
          //计算两指初始距离
          this.initTouchDis = Math.hypot(e.touches[0].pageX - e.touches[1].pageX, e.touches[0].pageY - e.touches[1].pageY)
          this.initScale = this.store.data.scale
          this.startTouches = e.touches
          this.touchCenter = {
            x: e.touches[0].pageX + (e.touches[1].pageX - e.touches[0].pageX) / 2 - this.clientRect.x,
            y: e.touches[0].pageY + (e.touches[1].pageY - e.touches[0].pageY) / 2 - this.clientRect.y
          }
          return
        } else if (e.touches.length === 3) {//三指
          this.store.emitter.emit('contextmenu', {
            e: {
              x,
              y,
              clientX: e.touches[0].clientX,
              clientY: e.touches[0].clientY,
              pageX: e.touches[0].pageX,
              pageY: e.touches[0].pageY
            },
            clientRect: this.clientRect
          })
          e.preventDefault()
          e.stopPropagation()
        }

        this.touchStartTimer = undefined
      }, 50)
    }

    this.handleTouchMove = (e) => {
      e.stopPropagation()
      e.preventDefault()
      const now = performance.now()
      if (now - this.touchStart < 50) {
        return
      }
      this.touchStart = now

      const touches = e.touches
      const touchCount = touches.length

      const x = e.touches[0].pageX - this.clientRect.x
      const y = e.touches[0].pageY - this.clientRect.y
      if (touchCount === 1) {
        this.handleMouseMove({
          x,
          y,
          clientX: e.changedTouches[0].clientX,
          clientY: e.changedTouches[0].clientY,
          pageX: e.changedTouches[0].pageX,
          pageY: e.changedTouches[0].pageY,
          ctrlKey: e.ctrlKey || e.metaKey,
          shiftKey: e.shiftKey,
          altKey: e.altKey,
          buttons: 1,
        })
      } else if (touchCount === 2 && this.startTouches?.length === 2) {
        //计算手势是缩放还是平移
        if (!this.touchMoving && !this.touchScaling) {
          const x1 = this.startTouches[0].pageX - touches[0].pageX
          const x2 = this.startTouches[1].pageX - touches[1].pageX
          const y1 = this.startTouches[0].pageY - touches[0].pageY
          const y2 = this.startTouches[1].pageY - touches[1].pageY
          if (((x1 >= 0 && x2 < 0) || (x1 <= 0 && x2 > 0)) && ((y1 >= 0 && y2 < 0) || (y1 <= 0 && y2 > 0))) {
            this.touchScaling = true
          } else {
            this.touchMoving = true
          }
        }

        //缩放
        if (this.touchScaling) {
          const scale = Math.hypot(touches[0].pageX - touches[1].pageX, touches[0].pageY - touches[1].pageY) / this.initTouchDis
          this.scale(this.initScale * scale, deepClone(this.touchCenter))
        }

        //平移
        if (this.touchMoving) {
          if (this.lastOffsetX) {
            const {scale} = this.store.data
            this.translate((x - this.lastOffsetX) / scale, (y - this.lastOffsetY) / scale)
          }
          this.lastOffsetX = x
          this.lastOffsetY = y
        }
      }
    }

    this.handleTouchEnd = (event) => {
      this.touchCenter = undefined
      this.touchScaling = undefined
      this.touchMoving = undefined
      this.startTouches = undefined
      this.lastOffsetX = 0
      this.lastOffsetY = 0

      const x = event.changedTouches[0].pageX - this.clientRect.x
      const y = event.changedTouches[0].pageY - this.clientRect.y

      this.handleMouseUp({
        x,
        y,
        clientX: event.changedTouches[0].clientX,
        clientY: event.changedTouches[0].clientY,
        pageX: event.changedTouches[0].pageX,
        pageY: event.changedTouches[0].pageY,
        ctrlKey: event.ctrlKey || event.metaKey,
        shiftKey: event.shiftKey,
        altKey: event.altKey,
        buttons: 1,
      })
      setTimeout(() => {
        this.render()
      }, 20)
    }

    /** 鼠标按下 */
    this.handleMouseDown = (e) => {
      if (e.buttons === 2) {
        this.mouseRight = MouseRight.Down
      }
      this.hideInput()
      if (e.buttons !== 1 && e.buttons !== 2) {
        this.hoverType = HoverType.None
        return
      }

      this.calibrateMouse(e)
      this.mousePos.x = e.x
      this.mousePos.y = e.y

      this.mouseDown = e
      this.lastMouseTime = performance.now()

      switch (this.hoverType) {
        case HoverType.None:
          this.inactive()
          break
        case HoverType.Node:
          if (this.store.hover) {
            const node = this.store.hover
            if (e.ctrlKey && !e.shiftKey || this.multiSelect) {
              if (node.calculative.active) {
                this.willInactivePen = node
              } else {
                node.calculative.active = true
                this.store.active.push(node)
                this.store.emitter.emit(EventType.ACTIVE, this.store.active)
              }
              this.patchFlags = true
            } else {
              this.inactive()
              this.active([node])
            }

            this.calcActiveRect()
          }
          break
        case HoverType.Copy:
          this.copy(this.store.active, false)
          this.paste()
          break
        case HoverType.Remove:
          this.delete(this.store.active, true, true)
          break
        case HoverType.Resize:
        case HoverType.ResizeByRatio:
          this.activeInitPos = []
          this.store.active.forEach((pen) => {
            this.activeInitPos.push({
              x: (pen.calculative.worldRect.x - this.activeRect.x) / this.activeRect.width,
              y: (pen.calculative.worldRect.y - this.activeRect.y) / this.activeRect.height,
            })
          })
          break
      }

      this.render()
    }

    /** 当前鼠标位置 */
    this.mousePos = {x: 0, y: 0}

    this.hoverTimer = 0

    /** 鼠标移动 */
    this.handleMouseMove = (e) => {
      // 防止异常情况导致mouseup事件没有触发
      if (
        this.mouseDown &&
        !this.mouseDown.restore &&
        e.buttons !== 1 &&
        e.buttons !== 2
      ) {
        this.handleMouseUp(e)
        return
      }
      // 避免鼠标点击和移动一起触发，误抖动
      if (this.lastMouseTime) {
        const now = performance.now()
        if (now - this.lastMouseTime < 50) {
          this.lastMouseTime = 0
          return
        }
        this.lastMouseTime = 0
      }

      //处理鼠标位置偏移
      this.calibrateMouse(e)
      //更新当前鼠标坐标
      this.mousePos.x = e.x
      this.mousePos.y = e.y

      if (this.mouseDown) {
        // 画布平移前提
        if (this.mouseRight === MouseRight.Down) {
          this.mouseRight = MouseRight.Translate
        }

        if (this.mouseRight === MouseRight.Translate) {
          const scale = this.store.data.scale
          let x = (e.x - this.mouseDown.x) / scale
          let y = (e.y - this.mouseDown.y) / scale
          e.shiftKey && !e.ctrlKey && (y = 0)
          e.ctrlKey && (x = 0)
          this.translate(x, y)
          return
        }

        // 旋转
        if (this.hoverType === HoverType.Rotate) {
          this.rotatePens({x: e.x, y: e.y})
          return
        }

        // 调整大小
        if (this.hoverType === HoverType.Resize || this.hoverType === HoverType.ResizeByRatio) {
          this.resizePens(e, this.hoverType === HoverType.ResizeByRatio)
          return
        }

        // 移动
        if (this.hoverType === HoverType.Node) {
          const x = e.x - this.mouseDown.x;
          const y = e.y - this.mouseDown.y;
          const shake = 20;
          if (e.ctrlKey && !e.shiftKey && (Math.abs(x) >= shake || Math.abs(y) >= shake)) {
            this.willInactivePen = undefined;
          }
          this.movePens(e);
          return;
        }
      }

      //获取当前hover对象类型
      const now = performance.now()
      if (now - this.hoverTimer > 50) {
        this.hoverTimer = now
        this.getHover(e)
      }

      this.render(false)
    }

    /** 鼠标松开 */
    this.handleMouseUp = (e) => {
      if (!this.mouseDown) {
        return
      }

      if (this.mouseRight === MouseRight.Down) {
        this.store.emitter.emit('contextmenu', {e, clientRect: this.clientRect})
      }
      this.mouseRight = MouseRight.None

      this.calibrateMouse(e)
      this.mousePos.x = e.x
      this.mousePos.y = e.y

      //预添加节点
      if (this.addCaches) {
        this.insertPensFromCache(this.addCaches, e);
        this.addCaches = undefined;
      }

      //旋转
      if (this.hoverType === HoverType.Rotate) {
        this.getSizeControlPoints()
      }

      //取消选中
      if (this.willInactivePen) {
        this.willInactivePen.calculative.active = undefined
        this.store.active.splice(this.store.active.findIndex((p) => p === this.willInactivePen), 1)
        this.calcActiveRect()
        this.willInactivePen = undefined
        this.store.emitter.emit(EventType.INACTIVE, [this.willInactivePen])
      }

      //缩放节点
      if (this.resizingPens) {
        this.resizedActivePens();
        this.resizingPens = undefined;
      }

      //旋转节点
      if (this.rotatingPens) {
        this.rotatedActivePens();
        this.rotatingPens = undefined;
      }

      //移动节点
      if (this.movingPens) {
        this.movedActivePens();
        this.movingPens = undefined;
      }

      this.mouseDown = undefined
      this.lastOffsetX = 0
      this.lastOffsetY = 0
      this.initActiveRect = undefined
      this.render(true)
    }

    /** 鼠标移出 */
    this.handleMouseLeave = () => {
      this.store.lastHover = undefined
    }

    /** 鼠标双击 */
    this.handleDoubleClick = (e) => {
      if (this.store.hover && this.store.hover.type == 'text') {
        this.showInput(this.store.hover)
      }

      this.store.emitter.emit('dblclick', {
        x: e.x,
        y: e.y,
        pen: this.store.hover,
      })
    }

    /** 失去焦点 */
    this.handleBlur = () => {
      this.mouseDown = null
    }

    /** 鼠标滚动 */
    this.handleWheel = (e) => {
      e.preventDefault();
      e.stopPropagation();

      // e.ctrlKey: false - 平移； true - 缩放。老windows触摸板不支持
      if (!e.ctrlKey) {
        this.translate(-e.deltaX, -e.deltaY);
        return;
      }

      let scaleOff = 0.015;
      let isMac = /mac os /i.test(navigator.userAgent);
      if (isMac) {
        if (!e.ctrlKey) {
          scaleOff *= e.wheelDeltaY / 240;
        } else if (e.deltaY > 0) {
          scaleOff *= -1;
        }
      } else {
        if (e.deltaY > 0) {
          scaleOff = -0.2;
        } else {
          scaleOff = 0.2;
        }
      }

      const {offsetX: x, offsetY: y} = e;
      this.scale(this.store.data.scale + scaleOff, {x, y});
      this.externalElements.focus();
    }

    this.wrapMouseEventHandler = (func, targetElement) => {
      function packedFunc(e) {
        if (targetElement && e.target !== targetElement) {
          return
        }
        func({
          x: e.offsetX,
          y: e.offsetY,
          clientX: e.clientX,
          clientY: e.clientY,
          pageX: e.pageX,
          pageY: e.pageY,
          ctrlKey: e.ctrlKey || e.metaKey,
          shiftKey: e.shiftKey,
          altKey: e.altKey,
          buttons: e.buttons
        })
      }

      return packedFunc
    }

    /** 监听事件 */
    this.listen = function () {
      globalThis.addEventListener('scroll', this.handleScroll)
      this.externalElements.addEventListener('gesturestart', e => e.preventDefault())
      this.externalElements.addEventListener('dragover', e => e.preventDefault())
      this.externalElements.addEventListener('drop', e => e.preventDefault())
      this.externalElements.addEventListener('contextmenu', e => e.preventDefault())
      this.externalElements.addEventListener('touchstart', this.handleTouchStart)
      this.externalElements.addEventListener('touchmove', this.handleTouchMove)
      this.externalElements.addEventListener('touchend', this.handleTouchEnd)
      this.externalElements.addEventListener('mousedown', this.wrapMouseEventHandler(this.handleMouseDown))
      this.externalElements.addEventListener('mousemove', this.wrapMouseEventHandler(this.handleMouseMove, this.externalElements))
      this.externalElements.addEventListener('mouseup', this.wrapMouseEventHandler(this.handleMouseUp))
      this.externalElements.addEventListener('mouseleave', this.wrapMouseEventHandler(this.handleMouseLeave))
      this.externalElements.addEventListener('dblclick', this.handleDoubleClick)
      this.externalElements.addEventListener('blur', this.handleBlur)
      this.externalElements.addEventListener('wheel', this.handleWheel)
    }

    //构造
    this.parentElement = parentElement
    setStyle(parentElement, {
      position: 'relative'
    })
    this.store = store

    this.canvas = document.createElement('canvas')
    setStyle(this.canvas, {
      position: 'absolute',
      left: '0px',
      top: '0px',
      right: '0px',
      bottom: '0px',
      margin: 'auto',
      width: '100%',
      height: '100%',
    })
    parentElement.appendChild(this.canvas)

    //处理界面缩放
    let iframe = document.createElement('iframe')
    setStyle(iframe, {
      position: 'absolute',
      left: 0,
      top: 0,
      width: '100%',
      height: '100%',
      border: 'none',
      opacity: 0
    })
    parentElement.insertBefore(iframe, parentElement.childNodes.item(0))
    if (iframe.contentWindow) {
      let resizeTimer = 0
      iframe.contentWindow.addEventListener("resize", () => {
        if (resizeTimer) {
          clearTimeout(resizeTimer)
        }
        resizeTimer = setTimeout(() => {
          this.resize()
          resizeTimer = undefined
        }, 100)
      })
    }

    /** 画布上层的div容器，用于放置其他工具 */
    this.externalElements = document.createElement('div')
    setStyle(this.externalElements, {
      position: 'absolute',
      left: '0px',
      top: '0px',
      right: '0px',
      bottom: '0px',
      margin: 'auto',
      outline: 'none',
      background: 'transparent',
      overflow: 'hidden',
      width: '100%',
      height: '100%',
    })
    parentElement.appendChild(this.externalElements)

    this.inputParent = document.createElement('div');
    this.input = document.createElement('textarea');
    this.createInput()

    let closeImage = ''
    loadImage(closeImage, img => {
      this.closeImg = img
      this.renderDelay(100)
    })

    let resizeImage = ''
    loadImage(resizeImage, img => {
      this.resizeImg = img
      this.renderDelay(100)
    })

    let rotateImage = ''
    loadImage(rotateImage, img => {
      this.rotateImg = img
      this.renderDelay(100)
    })

    let copyImage = ''
    loadImage(copyImage, img => {
      this.copyImg = img
      this.renderDelay(100)
    })

    this.listen()
  }

  /** 默认最大事件数 */
  const DEFAULT_MAX_EVENT_COUNT = 99999
  /** 所有事件 */
  const ALL_EVENT_NAME = '*'
  ImageDesigner.Emitter = function (maxEventCount) {

    /** 监听事件 */
    this.on = function (eventName, callback) {
      if (!eventName.length) {
        throw new Error("Param 'eventName' is required!")
      }
      if (!callback) {
        throw new Error("Param 'callback' is required!")
      }
      let eventList = this.eventMap[eventName]
      if (!eventList) {
        eventList = this.eventMap[eventName] = []
      }

      if (eventList.length < this.maxEventCount) {
        eventList.push(callback)
      }
    }

    /** 监听一次 */
    this.once = function (eventName, callback) {
      if (!eventName.length) {
        throw new Error("Param 'eventName' is required!")
      }
      if (!callback) {
        throw new Error("Param 'callback' is required!")
      }
      this.on(eventName, dealOnce(eventName, callback))
    }

    /** 处理监听一次的事件回调 */
    function dealOnce(eventName, callback) {
      let me = this
      let isCalled = false

      function packageFunc() {
        let args = []
        for (let i = 0; i < arguments.length; i++) {
          args.push(arguments[i])
        }
        me.off(eventName, packageFunc)
        if (!isCalled) {
          callback.apply(me, args)
        }
      }

      return packageFunc
    }

    /** 取消监听 */
    this.off = function (eventName, callback) {
      if (!eventName.length) {
        throw new Error("Param 'eventName' is required!")
      }
      if (!callback) {
        throw new Error("Param 'callback' is required!")
      }
      let eventList = this.eventMap[eventName]
      if (eventList) {
        let index = 0
        for (let i = 0; i < eventList.length; i++) {
          if (eventList[i] === callback) {
            this.eventMap[eventName].splice(i - index, 1)
            index++
          }
        }
      }
    }

    /** 根据事件名取消所有监听 */
    this.removeAll = function (eventName) {
      if (eventName?.length) {
        delete this.eventMap[eventName]
      } else {
        this.eventMap = {}
      }
    }

    /** 触发事件 */
    this.emit = function (eventName, arg) {
      let len = arguments.length
      let args = []
      for (let i = 0; i < len; i++) {
        args.push(arguments[i])
      }
      if (!eventName.length) {
        throw new Error("Param 'eventName' is required!")
      }
      let eventList = this.eventMap[eventName]
      if (eventList) {
        for (let i = 0; i < eventList.length; i++) {
          eventList[i].apply(this, args)
        }
      }
      //处理通用事件
      let allEventList = this.eventMap[ALL_EVENT_NAME]
      if (allEventList) {
        for (let i = 0; i < allEventList.length; i++) {
          allEventList[i].apply(this, args)
        }
      }
    }

    this.maxEventCount = maxEventCount ?? DEFAULT_MAX_EVENT_COUNT
    this.eventMap = {}
  }

  const globalStore = {}

  /** 取store，不存在在创建 */
  function useStore(id) {
    if (!globalStore[id]) {
      globalStore[id] = createStore()
      globalStore[id].id = id
    }
    return globalStore[id]
  }

  /** 创建store */
  function createStore() {
    return {
      pens: {},
      data: {
        width: 1920,
        height: 1080,
        backgroundImage: null,
        pens: [],
        x: 0,
        y: 0,
        scale: 1
      },
      active: [],
      histories: [],
      emitter: new ImageDesigner.Emitter(),
      dpiRatio: getDpiRatio()
    }
  }

  /** 清空store */
  function clearStore(store) {
    for (let key of Object.keys(store)) {
      delete store[key]
    }
    Object.assign(store, createStore())
  }

  /** 8位随机数 */
  function s8() {
    return (((1 + Math.random()) * 0x100000000) | 0).toString(16).substring(1)
  }

  /** 设置样式 */
  function setStyle(el, styleObj) {
    if (styleObj) {
      Object.keys(styleObj).forEach(key => {
        el.style[key] = styleObj[key]
      })
    }
  }

  /** 加载图片 */
  const imageCaches = {}

  function loadImage(image, callback) {
    if (image) {
      let img = imageCaches[image]

      const imageLoaded = () => {
        imageCaches[image] = img
        callback && callback(img)
      }

      if (img) {
        imageLoaded()
      } else {
        img = new Image()
        img.crossOrigin = 'anonymous'
        img.src = image
        img.onload = imageLoaded
      }
    }
  }

  /** 深度拷贝 */
  function deepClone(obj, keepCalc = false) {
    if (Array.isArray(obj)) {
      const arr = [];
      obj.forEach((item) => {
        arr.push(deepClone(item, keepCalc));
      });
      return arr;
    } else if (typeof obj === 'object') {
      if (obj === null) {
        return null;
      } else if (obj.constructor === RegExp) {
        return obj;
      }
      const result = {};
      for (const key in obj) {
        if (
          !obj.hasOwnProperty(key) ||
          ['canvas'].includes(key) ||
          obj[key] instanceof HTMLImageElement ||
          obj[key] instanceof HTMLMediaElement
        ) {
          if (keepCalc) {
            result[key] = obj[key]
          }
          continue;
        } else if (key === 'calculative' && !keepCalc) {
          continue;
        }
        result[key] = deepClone(obj[key], keepCalc);
      }
      return result;
    }

    return obj;
  }

  /** 根据中心点计算某点旋转角度后的坐标 */
  function rotatePoint(pt, angle, center) {
    if (!angle || angle % 360 === 0) {
      return
    }
    const a = (angle * Math.PI) / 180
    const x = (pt.x - center.x) * Math.cos(a) - (pt.y - center.y) * Math.sin(a) + center.x
    const y = (pt.x - center.x) * Math.sin(a) + (pt.y - center.y) * Math.cos(a) + center.y
    pt.x = x
    pt.y = y
  }

  /** 根据中心点计算缩放后某点的坐标 */
  function scalePoint(pt, scale, center) {
    pt.x = center.x - (center.x - pt.x) * scale;
    pt.y = center.y - (center.y - pt.y) * scale;
  }

  /** 计算一个点是否在另一个点的指定半径范围内 */
  function hitPoint(pt, target, radius = 5) {
    return (
      pt.x > target.x - radius &&
      pt.x < target.x + radius &&
      pt.y > target.y - radius &&
      pt.y < target.y + radius
    );
  }

  /** 判断一个点是否在矩形一定半径范围内 */
  function pointInSimpleRect(point, rect, r = 0) {
    const {x, y, ex, ey} = rect;
    return point.x >= x - r && point.x <= ex + r && point.y >= y - r && point.y <= ey + r;
  }

  /** 判断一个点是否在矩形内 */
  function pointInRect(point, rect) {
    if (!rect) {
      return;
    }
    if (rect.ex == null) {
      calcRightBottom(rect);
    }

    if (!rect.rotate || rect.rotate % 360 === 0) {
      return point.x > rect.x && point.x < rect.ex && point.y > rect.y && point.y < rect.ey;
    }

    if (!rect.center) {
      calcCenter(rect);
    }

    const pts = [
      {x: rect.x, y: rect.y},
      {x: rect.ex, y: rect.y},
      {x: rect.ex, y: rect.ey},
      {x: rect.x, y: rect.ey},
    ];
    pts.forEach((item) => {
      rotatePoint(item, rect.rotate, rect.center);
    });

    return pointInVertices(point, pts);
  }

  /** 判断一个点是否在多个点组成的多边形内 */
  function pointInVertices(point, vertices) {
    if (vertices.length < 3) {
      return false;
    }
    let isIn = false;
    let last = vertices[vertices.length - 1];
    for (const item of vertices) {
      if (last.y > point.y !== item.y > point.y) {
        if (item.x + ((point.y - item.y) * (last.x - item.x)) / (last.y - item.y) > point.x) {
          isIn = !isIn;
        }
      }
      last = item;
    }

    return isIn;
  }

  /** 计算中心点 */
  function calcCenter(rect) {
    if (!rect.center) {
      rect.center = {};
    }
    rect.center.x = rect.x + rect.width / 2;
    rect.center.y = rect.y + rect.height / 2;
  }

  /** 计算右下角 */
  function calcRightBottom(rect) {
    rect.ex = rect.x + rect.width;
    rect.ey = rect.y + rect.height;
  }

  /** 判断一个矩形是否在另一个矩形内 */
  function rectInRect(source, target, allIn) {
    if (source.rotate) {
      // 根据 rotate 扩大 rect
      source = getRectOfPoints(rectToPoints(source)) // 更改 source 引用地址值，不影响原值
    }
    if (allIn) {
      return (
        source.x > target.x &&
        source.ex < target.ex &&
        source.y > target.y &&
        source.ey < target.ey
      )
    }
    return !(
      source.x > target.ex ||
      source.ex < target.x ||
      source.ey < target.y ||
      source.y > target.ey
    )
  }

  /** 将矩形分解成点 */
  function rectToPoints(rect) {
    const pts = [
      {x: rect.x, y: rect.y},
      {x: rect.ex, y: rect.y},
      {x: rect.ex, y: rect.ey},
      {x: rect.x, y: rect.ey},
    ]

    if (rect.rotate) {
      if (!rect.center) {
        calcCenter(rect)
      }
      pts.forEach((pt) => {
        rotatePoint(pt, rect.rotate, rect.center)
      })
    }
    return pts
  }


  /** 根据一系列 */
  function getRect(pens) {
    const points = [];
    pens.forEach((pen) => {
      const rect = pen.calculative.worldRect;
      if (rect) {
        const pts = rectToPoints(rect);
        pts.forEach(p => {
          points.push(p)
        })
      }
    });

    const rect = getRectOfPoints(points);
    calcCenter(rect);
    return rect;
  }

  /** 根据一系列点获取一个能包围所有点的矩形 */
  function getRectOfPoints(points) {
    let x = Infinity
    let y = Infinity
    let ex = -Infinity
    let ey = -Infinity

    points?.forEach((item) => {
      if (!isFinite(item.x) || !isFinite(item.y)) {
        return
      }
      x = Math.min(x, item.x)
      y = Math.min(y, item.y)
      ex = Math.max(ex, item.x)
      ey = Math.max(ey, item.y)
    })
    return {x, y, ex, ey, width: ex - x, height: ey - y}
  }

  /** 缩放矩形 */
  function scaleRect(rect, scale, center) {
    if (!rect) {
      return;
    }
    rect.width *= scale;
    rect.height *= scale;
    scalePoint(rect, scale, center);

    calcRightBottom(rect);
    calcCenter(rect);
  }

  /** 移动矩形 */
  function translateRect(rect, x, y) {
    rect.x += x;
    rect.y += y;
    rect.ex += x;
    rect.ey += y;

    if (rect.center) {
      rect.center.x += x;
      rect.center.y += y;
    }
  }

  /**
   * 通过两条线段计算出相交的点
   * @param line1 线段1
   * @param line2 线段2
   */
  function getIntersectPoint(line1, line2) {
    const k1 = (line1.to.y - line1.from.y) / (line1.to.x - line1.from.x);
    const k2 = (line2.to.y - line2.from.y) / (line2.to.x - line2.from.x);
    return getIntersectPointByK(
      {
        k: k1,
        point: line1.from,
      },
      {
        k: k2,
        point: line2.from,
      }
    );
  }

  /**
   * 该方法作用同上，不过此方法需要传的是 斜率
   * @param line1 线段1
   * @param line2 线段2
   * @returns
   */
  function getIntersectPointByK(line1, line2) {
    if (isEqual(line1.k, 0)) {
      return {
        x: line2.point.x,
        y: line1.point.y,
      };
    } else if (isEqual(line2.k, 0)) {
      return {
        x: line1.point.x,
        y: line2.point.y,
      };
    }

    const b1 = line1.point.y - line1.k * line1.point.x;
    const b2 = line2.point.y - line2.k * line2.point.x;
    const x = (b2 - b1) / (line1.k - line2.k);
    const y = line1.k * x + b1;

    return {
      x,
      y,
    };
  }

  /** 判断两个值是否近似 */
  function isEqual(source, target) {
    // @ts-ignore
    return source.toFixed(12) == target;
  }

  /**
   * 缩放矩形
   *
   * @author zhengqiang
   * @since 2023-03-31
   */
  function resizeRect(rect, offsetX, offsetY, resizeIndex) {
    if (rect.rotate && rect.rotate % 360) {
      // 计算出外边的四个点
      const pts = rectToPoints(rect);
      // 斜率不改变，提前计算
      const k1 = (pts[0].y - pts[1].y) / (pts[0].x - pts[1].x);
      const k2 = (pts[1].y - pts[2].y) / (pts[1].x - pts[2].x);
      if (resizeIndex < 4) {
        // 斜对角的四个点
        // resize 的点
        pts[resizeIndex].x += offsetX;
        pts[resizeIndex].y += offsetY;
        // 不变的点
        const noChangePoint = pts[(resizeIndex + 2) % 4];
        // 由于斜率是不变的，我们只需要根据斜率 和 已知的两点求出相交的 另外两点
        pts[(resizeIndex + 1) % 4] = getIntersectPointByK(
          {k: resizeIndex % 2 ? k2 : k1, point: pts[resizeIndex]},
          {k: resizeIndex % 2 ? k1 : k2, point: noChangePoint}
        );
        pts[(resizeIndex + 4 - 1) % 4] = getIntersectPointByK(
          {k: resizeIndex % 2 ? k1 : k2, point: pts[resizeIndex]},
          {k: resizeIndex % 2 ? k2 : k1, point: noChangePoint}
        );
      } else {
        // 边缘四个点有两个点固定
        const k = [4, 6].includes(resizeIndex) ? k2 : k1;
        if (!isEqual(k, 0)) {
          pts[resizeIndex % 4].y += offsetY;
          pts[resizeIndex % 4].x += offsetY / k;
          pts[(resizeIndex + 1) % 4].y += offsetY;
          pts[(resizeIndex + 1) % 4].x += offsetY / k;
        } else {
          pts[resizeIndex % 4].x += offsetX;
          pts[(resizeIndex + 1) % 4].x += offsetX;
        }
      }
      if (
        (pts[0].x - pts[1].x) ** 2 + (pts[0].y - pts[1].y) ** 2 < 25 ||
        (pts[1].x - pts[2].x) ** 2 + (pts[1].y - pts[2].y) ** 2 < 25
      ) {
        // 距离小于 5 不能继续 resize 了
        return;
      }
      const retRect = pointsToRect(pts, rect.rotate);
      calcCenter(retRect);
      Object.assign(rect, retRect);
      return;
    }
    switch (resizeIndex) {
      case 0:
        if (rect.width - offsetX < 5 || rect.height - offsetY < 5) {
          break;
        }
        rect.x += offsetX;
        rect.y += offsetY;
        rect.width -= offsetX;
        rect.height -= offsetY;
        break;
      case 1:
        if (rect.width + offsetX < 5 || rect.height - offsetY < 5) {
          break;
        }
        rect.ex += offsetX;
        rect.y += offsetY;
        rect.width += offsetX;
        rect.height -= offsetY;
        break;
      case 2:
        if (rect.width + offsetX < 5 || rect.height + offsetY < 5) {
          break;
        }
        rect.ex += offsetX;
        rect.ey += offsetY;
        rect.width += offsetX;
        rect.height += offsetY;
        break;
      case 3:
        if (rect.width - offsetX < 5 || rect.height + offsetY < 5) {
          break;
        }
        rect.x += offsetX;
        rect.ey += offsetY;
        rect.width -= offsetX;
        rect.height += offsetY;
        break;
      case 4:
        if (rect.height - offsetY < 5) {
          break;
        }
        rect.y += offsetY;
        rect.height -= offsetY;
        break;
      case 5:
        if (rect.width + offsetX < 5) {
          break;
        }
        rect.ex += offsetX;
        rect.width += offsetX;
        break;
      case 6:
        if (rect.height + offsetY < 5) {
          break;
        }
        rect.ey += offsetY;
        rect.height += offsetY;
        break;
      case 7:
        if (rect.width - offsetX < 5) {
          break;
        }
        rect.x += offsetX;
        rect.width -= offsetX;
        break;
    }
  }

  /**
   * 通过 4 个点和旋转角度，计算出原矩形（旋转前的矩形）
   * @param pts 4 个点
   * @param rotate 旋转角度
   */
  function pointsToRect(pts, rotate) {
    // 1. 计算 center，认为 0，2 ；1，3 的连线相交就是 center 点
    const center = getIntersectPoint(
      {
        from: pts[0],
        to: pts[2],
      },
      {
        from: pts[1],
        to: pts[3],
      }
    );
    // 2. 把点反向转 rotate °
    for (const pt of pts) {
      rotatePoint(pt, -rotate, center);
    }
    // 3. 计算区域
    return getRectOfPoints(pts);
  }

  /** 计算旋转角度 */
  function calcRotate(point, center) {
    if (point.x === center.x) {
      return point.y <= center.y ? 0 : 180;
    }

    if (point.y === center.y) {
      return point.x < center.x ? 270 : 90;
    }

    const x = point.x - center.x;
    const y = point.y - center.y;
    let angle = (Math.atan(Math.abs(x / y)) / (2 * Math.PI)) * 360;
    if (x > 0 && y > 0) {
      angle = 180 - angle;
    } else if (x < 0 && y > 0) {
      angle += 180;
    } else if (x < 0 && y < 0) {
      angle = 360 - angle;
    }

    return angle;
  }

  function getDpiRatio() {
    let dpiRatio = globalThis.devicePixelRatio || 1
    if (dpiRatio < 1) {
      dpiRatio = 1
    } else if (dpiRatio > 1 && dpiRatio < 1.5) {
      dpiRatio = 1.5
    }
    return dpiRatio
  }

  globalThis.ImageDesigner = ImageDesigner
})(window)
